package org.lionsoul.websnail.store;

import org.fusesource.leveldbjni.JniDBFactory;
import org.iq80.leveldb.*;

import java.io.File;
import java.io.IOException;
import java.util.Map.Entry;

/**
 * Persistent todo list base on levelDB
 *
 * @author chenxin<chenxin619315 @ gmail.com>
 */
public class LevelDBTodo implements Todo {
    private static final JniDBFactory factory = JniDBFactory.factory;

    /**
     * level DB instance
     */
    private DB db = null;
    private ReadOptions rdOpt = null;
    private WriteOptions wtOpt = null;

    /**
     * Serials of construct method,
     * so you can define the DB engine yourself if you know level DB well
     */
    public LevelDBTodo(String dbFile) throws IOException {
        this(dbFile, getDefaultOptions());
    }

    public LevelDBTodo(String dbFile, Options options) throws IOException {
        this(dbFile, options, new ReadOptions(), new WriteOptions());
    }

    public LevelDBTodo(String dbFile, Options options,
                       ReadOptions rdOpt, WriteOptions wtOpt) throws IOException {
        File file;
        if (dbFile.endsWith(File.pathSeparator)) {
            file = new File(dbFile + "todo");
        } else {
            file = new File(dbFile + File.separator + "todo");
        }
        if (!file.exists()) {
            file.mkdirs();
        }

        if (null == options) {
            options = getDefaultOptions();
        }
		
        file = new File(dbFile + "_todo");
        this.rdOpt = rdOpt;
        this.wtOpt = wtOpt;
        this.db = factory.open(file, options);
    }

    /**
     * default options settings
     *
     * @return Options
     */
    private static Options getDefaultOptions() {
        Options options = new Options();
        options.createIfMissing(true);
        options.errorIfExists(false);
        /// options.cacheSize(30 * 1048576);    //30MB cache

        return options;
    }

    /**
     * auto key generate base on the Unix time stamp
     * this will make the implementation more like a traditional FIFO queue
     */
    private static final Object counterLock = new Object();
    private static int counter = 0;

    public static byte[] key(String url) {
        synchronized (counterLock) {
            counter++;
        }

        /*
         * modified at 2017/07/25 by will
         * 	 change the mode base on unix stamp to id auto-increment mode
         */
        long seed = counter;

        return new byte[]{
                (byte) (seed >>> 56),
                (byte) (seed >>> 48),
                (byte) (seed >>> 40),
                (byte) (seed >>> 32),
                (byte) (seed >>> 24),
                (byte) (seed >>> 16),
                (byte) (seed >>> 8),
                (byte) (seed)
        };
    }

    @Override
    public synchronized boolean add(String url) {
        db.put(key(url), JniDBFactory.bytes(url), wtOpt);
        return true;
    }

    @Override
    public synchronized boolean add(String[] urls) {
        WriteBatch batch = db.createWriteBatch();
        for (String url : urls) {
            batch.put(key(url), JniDBFactory.bytes(url));
        }

        db.write(batch, wtOpt);

        try {
            batch.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return true;
    }

    @Override
    public synchronized boolean addFirst(String url) {
        return add(url);
    }

    @Override
    public synchronized boolean addFirst(String[] urls) {
        return add(urls);
    }

    @Override
    public synchronized void clear() {
        /*
         * delete all the records
         * well, a better way should be replaced for this
         */
        DBIterator it = db.iterator(rdOpt);
        for (it.seekToFirst(); it.hasNext(); ) {
            Entry<byte[], byte[]> e = it.next();
            db.delete(e.getKey(), wtOpt);
        }

        try {
            it.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public synchronized String shift() {
        String val = null;

        DBIterator it = db.iterator(rdOpt);
        it.seekToFirst();
        if (it.hasNext()) {
            Entry<byte[], byte[]> e = it.next();
            val = JniDBFactory.asString(e.getValue());
            db.delete(e.getKey(), wtOpt);
        }

        try {
            it.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return val;
    }

    @Override
    public synchronized String pop() {
        String val = null;

        DBIterator it = db.iterator(rdOpt);
        it.seekToLast();
        if (it.hasNext()) {
            Entry<byte[], byte[]> e = it.prev();
            val = JniDBFactory.asString(e.getValue());
            db.delete(e.getKey(), wtOpt);
        }

        try {
            it.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return val;
    }

    @Override
    public synchronized int size() {
        return 0;
    }

    @Override
    public synchronized void close() {
        try {
            db.close();
            db = null;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
